package com.gosling.cloudspace.tools.loanpLan;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.gosling.cloudspace.common.constants.CommonConstants;
import com.gosling.cloudspace.common.constants.order.BillRepayStatusConstants;
import com.gosling.cloudspace.common.entity.dtos.CalculatorVO;
import com.gosling.cloudspace.common.entity.dtos.LoanPlanInfoVO;
import com.gosling.cloudspace.common.exception.PrometheusException;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

/**
 * 等额本息计算工具类
 *
 * @author lihonglin
 * @date 2023/10/24 8:52
 * <p>
 * 分为账单和订单两个主体
 * 账单始终只有一个，而订单则为用户借款笔数
 * <p>
 * 等额本息还款，也称定期付息，即借款人每月按相等的金额偿还贷款本息，其中每月贷款利息按月初剩余贷款本金计算并逐月结清。把按揭贷款的本金总额与利息总额相加，
 * 然后平均分摊到还款期限的每个月中。作为还款人，每个月还给银行固定金额，但每月还款额中的本金比重逐月递增、利息比重逐月递减。
 * <p>
 */
@Slf4j
public class LoanPlanByStagesTool {

    private static final BigDecimal NUM_12 = new BigDecimal("12");
    private static final BigDecimal NUM_100 = new BigDecimal("100");
    private static final BigDecimal NUM_360 = new BigDecimal("360");
    private static final BigDecimal PERCENT_25 = new BigDecimal("0.25");
    private static final int SCALE = 7;
    private static final int SCALE_2 = 2;
    private static final RoundingMode MODE = RoundingMode.UP;

    //首次逾期时间，首次还款时间，最后一次逾期时间，最后一次还款日志
    private static final String FIRST_DUE_DATE = "firstDueDate";
    private static final String FIRST_REPAY_DATE = "firstRepayDate";
    private static final String LAST_DUE_DATE = "lastDueDate";
    private static final String LAST_REPAY_DATE = "lastRepayDate";

    private static Supplier<PrometheusException> tryExp(String msg) {
        return () -> new PrometheusException(msg);
    }

    /***
     * 提前结清计算总额=本金+利息+提前结清手续费-减免利息
     * @param principal         本金
     * @param interest          利息
     * @param earlyFee          提结手续费
     * @param reduceInterest    减免利息
     * @return
     */
    public static BigDecimal earlyTotalAmt(BigDecimal principal, BigDecimal interest, BigDecimal earlyFee, BigDecimal reduceInterest) {
        return principal.add(interest).add(earlyFee).subtract(reduceInterest).setScale(2, RoundingMode.UP);
    }

    /**
     * 当前时间-提前结清需要实时计算利息
     * <p>
     * 公式：提前结清利息= 贷款剩余本金×产品利率*（当前日期-上次实际还款日期）
     *
     * @param invest        贷款剩余本金
     * @param rate          还款总月数
     * @param lastRepayDate 上次实际还款日期
     * @return 每月偿还本金和利息(入1 单位分)
     */
    public static BigDecimal getRepayEarlyInterest(BigDecimal invest, BigDecimal rate, LocalDate lastRepayDate, Boolean type) {
        // 获取当前日期
        LocalDate currentDate = LocalDate.now();
        // 计算剩余天数
        // 判断当天是不是还款成功过
        // 如果还款成功就不计算今天的利息
        // 最近还款日不等于空且计算天数等于0且还款状态等于ture,利息等于0
        // 计算剩余天数 不足一天按一天算
        long daysBetween = ChronoUnit.DAYS.between(lastRepayDate, currentDate);
        if (daysBetween == 0 && !type) {
            daysBetween = 1;
        }
        // 日利率 360
        BigDecimal dayRate = rate.divide(NUM_360, SCALE, MODE);
        // 计算提前结清利息
        return invest.multiply(dayRate).multiply(BigDecimal.valueOf(daysBetween)).setScale(2, RoundingMode.UP);
    }

    /**
     * 根据线下还款时间-提前结清需要实时计算利息
     * <p>
     * 公式：提前结清利息= 贷款剩余本金×产品利率*（当前日期-上次实际还款日期）
     *
     * @param invest           贷款剩余本金
     * @param rate             还款总月数
     * @param lastRepayDate    上次实际还款日期
     * @param offlineRepayDate 线下还款时间
     * @return 每月偿还本金和利息(入1 单位分)
     */
    public static BigDecimal getRepayEarlyInterest(BigDecimal invest, BigDecimal rate, LocalDate lastRepayDate, LocalDate offlineRepayDate, boolean type) {
        // 计算剩余天数 判断当天是不是还款成功过
        // 如果还款成功就不计算今天的利息
        // 最近还款日不等于空且计算天数等于0且还款状态等于ture,利息等于0
        // 计算剩余天数 不足一天按一天算
        long daysBetween = ChronoUnit.DAYS.between(lastRepayDate, offlineRepayDate);
        if (daysBetween == 0 && !type) {
            daysBetween = 1;
        }
        // 日利率 360
        BigDecimal dayRate = rate.divide(NUM_360, SCALE, MODE);
        // 计算提前结清利息
        return invest.multiply(dayRate).multiply(BigDecimal.valueOf(daysBetween)).setScale(2, RoundingMode.UP);
    }


    /**
     * 每月偿还本金和利息
     * <p>
     * 公式：每月偿还本息=〔贷款本金×月利率×(1＋月利率)＾还款月数〕÷〔(1＋月利率)＾还款月数-1〕
     *
     * @param invest     总借款额（贷款本金,单位分）
     * @param totalMonth 还款总月数
     * @return 每月偿还本金和利息(入1 单位分)
     */
    public static BigDecimal getPerMonthPrincipalInterest(BigDecimal invest, int totalMonth, BigDecimal monthRate) {
        BigDecimal onePlusRate = BigDecimal.ONE.add(monthRate);
        BigDecimal pow = onePlusRate.pow(totalMonth);
        BigDecimal v = invest.multiply((monthRate.multiply(pow)));
        //BigDecimal z = BigDecimal.valueOf(Math.pow(BigDecimal.ONE.add(monthRate).doubleValue(), totalMonth) - 1);
        BigDecimal z = (BigDecimal.ONE.add(monthRate).pow(totalMonth)).subtract(BigDecimal.ONE);
        return v.divide(z, SCALE_2, MODE);
    }

    /**
     * 等额本息的每月偿还利息
     * <p>
     * 公式：每月偿还利息=贷款本金×月利率×〔(1+月利率)^还款月数-(1+月利率)^(还款月序号-1)〕÷〔(1+月利率)^还款月数-1〕
     *
     * @param invest     总借款额（贷款本金,分）
     * @param totalMonth 还款总月数
     * @return 每月偿还利息(入1 单位分)
     */
    public static Map<Integer, BigDecimal> getPerMonthInterest(BigDecimal invest, int totalMonth, BigDecimal monthRate) {
        Map<Integer, BigDecimal> map = new HashMap<>();
        //1+月利率
        BigDecimal onePlusRate = BigDecimal.ONE.add(monthRate);
        BigDecimal monthInterest;
        for (int i = 1; i < totalMonth + 1; i++) {
            BigDecimal multiply = invest.multiply(monthRate);
            //double sub = Math.pow(a, totalMonth) - Math.pow(a, i - 1);
            BigDecimal pow = onePlusRate.pow(totalMonth);
            BigDecimal sub = pow.subtract(onePlusRate.pow(i - 1));
            //monthInterest = multiply.multiply(new BigDecimal(sub)).divide(new BigDecimal(Math.pow(a, totalMonth) - 1), SCALE, MODE);
            monthInterest = multiply.multiply(sub).divide(pow.subtract(BigDecimal.ONE), SCALE, MODE);
            map.put(i, monthInterest);
        }
        return map;
    }


    /**
     * 等额本息的每月偿还本金（月还本息-月还利息）
     *
     * @param invest     总借款额（贷款本金,分）
     * @param totalMonth 还款总月数
     * @return 每月偿还本金(取整舍 单位分)
     */
    public Map<Integer, BigDecimal> getPerMonthPrincipal(BigDecimal invest, int totalMonth, BigDecimal monthRate) {
        //1 + monthRate
        BigDecimal onePlusRate = BigDecimal.ONE.add(monthRate);
        BigDecimal pow = onePlusRate.pow(totalMonth);
        BigDecimal pow_sub_1 = pow.subtract(BigDecimal.ONE);
        BigDecimal monthIncome = invest.multiply(monthRate).multiply(pow.divide(pow_sub_1, SCALE, MODE));
        Map<Integer, BigDecimal> mapPrincipal = new HashMap<>();
        BigDecimal monthInterest;
        for (int i = 1; i < totalMonth + 1; i++) {
            BigDecimal multiply = invest.multiply(monthRate);
            //double sub = pow - (Math.pow(a, i - 1));
            BigDecimal sub = pow.subtract(onePlusRate.pow(i - 1));
            monthInterest = multiply.multiply(sub).divide(pow_sub_1, SCALE, MODE);
            mapPrincipal.put(i, monthIncome.subtract(monthInterest));
        }
        return mapPrincipal;
    }

    /**
     * 等额本息的总利息
     *
     * @param invest     总借款额（贷款本金）
     * @param totalMonth 还款总月数
     * @return 总利息 (单位分)
     */
    public BigDecimal getInterestCount(BigDecimal invest, int totalMonth, BigDecimal monthRate) {
        BigDecimal count = new BigDecimal("0");
        Map<Integer, BigDecimal> mapInterest = getPerMonthInterest(invest, totalMonth, monthRate);
        for (Map.Entry<Integer, BigDecimal> entry : mapInterest.entrySet()) {
            count = count.add(entry.getValue());
        }
        return count;
    }

    /***
     * 计算下一个还款日
     *
     * 产品管理模块配置还款日类型：“对日还款”或“固定还款日”
     * 还款日类型-对日还款（25号以后按25号算）
     * 还款日类型-固定还款日（只能选1-25号，含有步长 x）当为固定还款日还款时可设定放款日到还款日间隔天数顺延月份的功能；
     *
     * @param now           当前时间
     * @param repayDay      已有还款日
     * @param x             旧还款日和当前时间相差天数
     * @return
     */
    @ApiModelProperty("计算下一个还款日")
    public LocalDate getNexMonDate(LocalDate now, Integer repayDay, Integer x, String repayDayType) {
        int dayOfMonth = now.getDayOfMonth();
        //计算下一个还款日
        LocalDate nexMonDate = null;
        //对日还款
        if (CommonConstants.REPAY_DAY_FORDAY.equals(repayDayType)) {
            if (dayOfMonth > 25) {
                //还款日-固定为25号
                nexMonDate = nexDate(now, 25, now.getMonthValue() + 1);
            } else {
                nexMonDate = now.plusMonths(1);
            }
        } else {
            Assert.isFalse(ObjectUtil.isNull(x), tryExp("固定还款日时,间隔天数未配置"));
            //取当前时间的“年”，“月”，取已有还款日的“日”
            //x为：当前日期和已有还款日之间的天数
            LocalDate nex = nexDate(now, repayDay, now.getMonthValue());
            LocalDate nex2 = nexDate(now, repayDay, now.getMonthValue() + 1);
            //如果还款日大于25且非首次借款（已有还款日）
            //如果（今天）25 > （还款日）22 证明已经过了当月还款日 延后至下一个月还款日
            if (dayOfMonth > repayDay) {
                //下个月22号
                nex = nex2;
                nex2 = nex2.plusMonths(1);
            }
            if (ChronoUnit.DAYS.between(now, nex) < x) {
                //放款日距离账日小于15天，该借款订单首期还款顺延到下一个还款日，
                nexMonDate = nex2;
            } else {
                //放款日距离账日大于15天，则用已有还款日
                nexMonDate = nex;
            }
        }
        return nexMonDate;
    }

    /***
     * 计算超出的时间
     * @param now
     * @param dayOfMonth
     * @param monthPlus
     * @return
     */
    private LocalDate nexDate(LocalDate now, int dayOfMonth, int monthPlus) {
        int year = now.getYear();
        if (monthPlus > 12) {
            return LocalDate.of(year + 1, monthPlus - 12, dayOfMonth);
        }
        return LocalDate.of(year, monthPlus, dayOfMonth);
    }

    /***
     * 逾期计划---针对订单（非账单）
     * 罚息主体---当期本金：逾期天数 * 逾期本金 * 罚息率 = 罚息    挂在每个逾期的月份上
     * 罚息主体---剩余本金：逾期天数 * 剩余本金 * 罚息率 = 罚息    挂在第一个逾期的月份上
     *
     * @param planList              还款计划
     * @param punishInterestRate    罚息
     * @param punishSubject         罚息主体
     * @return
     */
    @ApiModelProperty("计算逾期计划")
    public List<LoanPlanInfoVO> calculatorOverdue(List<LoanPlanInfoVO> planList,
                                                  BigDecimal punishInterestRate,
                                                  String punishSubject,
                                                  String lastRepayDateTime) {
        Assert.isFalse(CollUtil.isEmpty(planList), tryExp("请输入有效的还款计划"));
        Assert.isFalse(!CommonConstants.PUNISH_SUB_LIST.contains(punishSubject), tryExp("罚息主体不合法"));
        Assert.isFalse(punishInterestRate.compareTo(BigDecimal.ZERO) < 0, tryExp("罚息利率不合法"));
        LoanPlanInfoVO vo = planList.get(0);
        Integer userId = vo.getUserId();
        String orderNo = vo.getLoanOrderNo();
        log.info("before--用户id:{},订单号:{},罚息开始计算,======================start===================", userId, orderNo);
        log.info("before--计算前完整计划为:{}", JSONUtil.toJsonStr(planList));
        //当前时间
        //LocalDate now = LocalDate.of(2024, 1, 15);
        LocalDate now = LocalDate.now();
        //日罚息
        BigDecimal dayPunishInterestRate = punishInterestRate.divide(NUM_360, SCALE, MODE);
        //1、当期是否逾期，用于计算逾期天数
        //2、当期是否结清，当期结清从下一起开始计算
        //3、如果罚息主体是当期本金，那么需要对已经逾期的期数进行计算罚息，分开挂在月份期数上
        //4、如果罚息主体是所有本金，那么需要对所有本金进行计算罚息，并只需要挂在当前要还的月份期数上
        Map<String, LocalDate> dueDateMap = new HashMap<>();
        //最后一次还款时间
        LocalDate lastRepayDate = putLastRepayDate(lastRepayDateTime, dueDateMap);
        log.info("最后一次还款时间:{}", lastRepayDate);
        //保存上一期剩余本金
        Map<String, BigDecimal> preSurplusMap = setSurplusLoanAmt(planList);
        //罚息主体为---当期本金为
        currentPrincipal(planList, punishSubject, now, dayPunishInterestRate, dueDateMap);
        //将逾期的利息挂在第一个逾期的头上
        resetInterestAmt(planList, now, dueDateMap, preSurplusMap);
        //罚息主体为---剩余本金
        surplusPrincipal(planList, punishSubject, now, dayPunishInterestRate, preSurplusMap, lastRepayDate);
        //处理第一个没有逾期的利息，从正常的期数开始查找，第一个正常的期数
        resetPayableInterest(planList, now, dueDateMap, preSurplusMap);
        //重新计算应还金额
        resetPayableAmt(planList);
        log.info("after--用户id:{},订单号:{},罚息计算完成,======================end===================", userId, orderNo);
        log.info("after--计算后完整计划为:{}", JSONUtil.toJsonStr(planList));
        return planList;
    }

    /***
     * 最后一次还款时间
     * @param lastRepayDateTime
     * @param dueDateMap
     * @return
     */
    private LocalDate putLastRepayDate(String lastRepayDateTime, Map<String, LocalDate> dueDateMap) {
        //最后一次还款时间
        LocalDate lastRepayDate = null;
        if (StrUtil.isNotBlank(lastRepayDateTime)) {
            lastRepayDate = cn.hutool.core.date.DateUtil.parseLocalDateTime(lastRepayDateTime).toLocalDate();
        }
        dueDateMap.put(LAST_REPAY_DATE, lastRepayDate);
        return lastRepayDate;
    }

    /***
     * 罚息主体为---当期本金---挂在每个逾期的月份上
     *
     * @param planList
     * @param punishSubject
     * @param now
     * @param dayPunishInterestRate
     * @param dueDateMap
     */
    private void currentPrincipal(List<LoanPlanInfoVO> planList, String punishSubject, LocalDate now, BigDecimal dayPunishInterestRate, Map<String, LocalDate> dueDateMap) {
        for (LoanPlanInfoVO plan : planList) {
            //应还款时间
            LocalDate payableDate = plan.getPayableDate();
            //还款状态
            String repayStatus = plan.getRepayStatus();
            //如果已还，则为已还款时间
            if (BillRepayStatusConstants.NORMAL_REPAID.equals(repayStatus)
                    || BillRepayStatusConstants.OVERDUE_REPAID.equals(repayStatus)
                    || BillRepayStatusConstants.EARLY.equals(repayStatus)) {
                //首次还款时间
                if (ObjectUtil.isNull(dueDateMap.get(FIRST_REPAY_DATE))) {
                    dueDateMap.put(FIRST_REPAY_DATE, payableDate);
                }
            }
            if (BillRepayStatusConstants.OVERDUE.equals(repayStatus)) {
                log.info("逾期期数为:{}-逾期应还时间为:{}", plan.getPeriodsNum(), payableDate);
                //逾期天数
                Long overdueDays = setOverdueDays(now, payableDate);
                plan.setOverdueDays(overdueDays.intValue());
                //首次逾期时间
                if (ObjectUtil.isNull(dueDateMap.get(FIRST_DUE_DATE))) {
                    dueDateMap.put(FIRST_DUE_DATE, payableDate);
                } else {
                    //其余逾期挂0利息
                    plan.setPayableInterestAmt(BigDecimal.ZERO);
                }
                //罚息主体为---当期本金
                if (punishSubject.equals(CommonConstants.PUNISH_SUB_CURR)) {
                    //计算逾期天数
                    BigDecimal payablePrincipalAmt = plan.getPayablePrincipalAmt();
                    //罚息主体---当期本金：逾期天数 * 逾期本金 * 罚息率 = 罚息，挂在每个逾期的月份上
                    BigDecimal punishRate = dayPunishInterestRate.multiply(new BigDecimal(overdueDays)).multiply(payablePrincipalAmt);
                    //罚息
                    plan.setPunishInterestRate(punishRate.setScale(SCALE_2, MODE));
                }
                //最近一次逾期时间
                dueDateMap.put(LAST_DUE_DATE, payableDate);
            }
        }
        log.info("首次逾期时间和最近一次逾期时间,{},", JSONUtil.toJsonStr(dueDateMap));
    }

    /***
     * 通用处理（不用区分）-将逾期的利息挂在第一个逾期的头上
     * @param planList
     * @param now
     * @param dueDateMap
     */
    private void resetInterestAmt(List<LoanPlanInfoVO> planList, LocalDate now, Map<String, LocalDate> dueDateMap, Map<String, BigDecimal> preSurplusMap) {
        //将逾期的利息挂在第一个逾期的头上
        for (LoanPlanInfoVO plan : planList) {
            if (BillRepayStatusConstants.OVERDUE.equals(plan.getRepayStatus())) {
                LocalDate firstDueDate = dueDateMap.get(FIRST_DUE_DATE);
                //最后一次还款时间到今天的利息
                Long interestDays = interestDays(now, dueDateMap, plan);
                //第一个逾期的时间
                if (firstDueDate.compareTo(plan.getPayableDate()) == 0) {
                    //剩余本金
                    BigDecimal invest = preSurplusMap.get(String.valueOf(plan.getPeriodsNum()));
                    //月利率
                    BigDecimal monthRate = plan.getDayRate().multiply(new BigDecimal(interestDays));
                    //当月利息
                    BigDecimal monthInterest = invest.multiply(monthRate).setScale(SCALE_2, MODE);
                    plan.setPayableInterestAmt(monthInterest);
                }
            }
        }
    }

    /***
     * 重新计算应还金额、总应还金额
     * @param planList
     */
    private void resetPayableAmt(List<LoanPlanInfoVO> planList) {
        BigDecimal t = new BigDecimal(0);
        for (LoanPlanInfoVO plan : planList) {
            //当月应还本金
            BigDecimal pa = plan.getPayablePrincipalAmt();
            //当月利息
            BigDecimal pia = plan.getPayableInterestAmt();
            //当月应还罚息
            BigDecimal pir = ObjectUtil.isNull(plan.getPunishInterestRate()) ? BigDecimal.ZERO : plan.getPunishInterestRate();
            //当月应还金额 = 本金+利息+罚息9
            plan.setPayableAmt(pa.add(pia).add(pir).setScale(SCALE_2, MODE));
            t = t.add(plan.getPayableAmt());
        }
        //重新计算总应还金额
        BigDecimal total = t;
        planList.forEach(p -> p.setTotalPayableAmt(total));
    }

    /***
     * 重新计算-逾期的下一期正常期数的应还利息
     *
     * @param planList
     * @param now
     * @param dueDateMap
     */
    private void resetPayableInterest(List<LoanPlanInfoVO> planList, LocalDate now, Map<String, LocalDate> dueDateMap, Map<String, BigDecimal> preSurplusMap) {
        for (LoanPlanInfoVO plan : planList) {
            //修改第一个正常的期数的利息 应该会比正常的少
            if (BillRepayStatusConstants.NORMAL.equals(plan.getRepayStatus())) {
                //应还款时间
                LocalDate payableDate = plan.getPayableDate();
                //正常-未还款情况
                if (payableDate == now) {
                    //如果当前时间和应还日期一致，则说明刚好在还款日，并未有新的逾期
                    break;
                }
                //最后一次逾期时间
                LocalDate lastDueDate = dueDateMap.get(LAST_DUE_DATE);
                if (ObjectUtil.isNull(lastDueDate)) {
                    continue;
                }
                //当前时间到下一个还款日,计算当月利息
                long betweenDays = Math.abs(ChronoUnit.DAYS.between(now, payableDate));
                BigDecimal invest = preSurplusMap.get(String.valueOf(plan.getPeriodsNum()));
                BigDecimal monthRate = plan.getDayRate().multiply(new BigDecimal(betweenDays));
                BigDecimal monthInterest = invest.multiply(monthRate).setScale(SCALE_2, MODE);
                plan.setPayableInterestAmt(monthInterest);
                return;
            }
        }
    }

    /***
     * 剩余本金方式计算罚息
     *
     * @param planList
     * @param punishSubject
     * @param now
     * @param dpir
     */
    private void surplusPrincipal(List<LoanPlanInfoVO> planList,
                                  String punishSubject,
                                  LocalDate now,
                                  BigDecimal dpir,
                                  Map<String, BigDecimal> preSurplusMap,
                                  LocalDate lastRepayDate) {
        //罚息主体为---剩余本金
        if (punishSubject.equals(CommonConstants.PUNISH_SUB_SURP)) {
            for (LoanPlanInfoVO plan : planList) {
                if (!BillRepayStatusConstants.OVERDUE.equals(plan.getRepayStatus())) {
                    continue;
                }
                //最后一次还款时间 到 今天的时间
                LocalDate payableDate = ObjectUtil.isNotNull(lastRepayDate) ? lastRepayDate : plan.getPayableDate();
                //逾期天数
                Long overdueDays = Math.abs(ChronoUnit.DAYS.between(now, payableDate));
                //罚息主体为---剩余本金
                BigDecimal surplusPrincipalAmt = preSurplusMap.get(String.valueOf(plan.getPeriodsNum()));
                //罚息主体---剩余本金：逾期天数 * 剩余本金 * 罚息率 = 罚息，挂在第一个逾期的月份上
                BigDecimal punishRate = dpir.multiply(new BigDecimal(overdueDays)).multiply(surplusPrincipalAmt);
                //罚息
                plan.setPunishInterestRate(punishRate.setScale(SCALE_2, MODE));
                break;
            }
        }
    }

    /***
     * 计算-方式二：日利率 * 当月天数 = 月利率
     *
     * 正常还款
     *
     * @param req
     * @return
     */
    @ApiModelProperty("正常还款计划")
    public List<LoanPlanInfoVO> calculator(CalculatorVO req) {
        // invest                本金
        // periodsNum            分期期数
        // yearRate              年利率
        // date                  分期开始时间
        // oldRepayDate          以前有的分期开始时间（有就传，没有就是首次）
        // financingYearRate     融担年利率
        // financingPeriodsNum   融担分期
        // orderNo               订单号 每次唯一
        // repayDayPostpone      间隔天数
        BigDecimal invest = req.getInvest();
        Integer periodsNum = req.getPeriodsNum();
        BigDecimal yearRate = req.getYearRate();
        LocalDate date = req.getDate();
        Integer oldRepayDate = req.getOldRepayDate();
        BigDecimal financingYearRate = req.getFinancingYearRate();
        Integer financingPeriodsNum = req.getFinancingPeriodsNum();
        String orderNo = req.getOrderNo();
        Integer userId = req.getUserId();
        String repayDayType = req.getRepayDayType();
        Integer repayDayPostpone = req.getRepayDayPostpone();
        //boolean isFinan = ObjectUtil.isAllNotEmpty(financingYearRate, financingPeriodsNum);
        //TODO 融担开关 后期若有需要则打开
        boolean isFinan = false;
        //必填参数校验
        Assert.notNull(invest, tryExp("借款金额必填"));
        Assert.notNull(orderNo, tryExp("订单号必填"));
        Assert.notNull(periodsNum, tryExp("借款期数必填"));
        Assert.notNull(yearRate, tryExp("年化利率必填"));
        Assert.notNull(date, tryExp("借款开始时间必填"));
        Assert.isFalse(periodsNum <= 0, tryExp("分期数不合法"));
        Assert.notNull(repayDayType, tryExp("还款日类型未配置"));
        //金额相关必须大于等于0 年化利率最高位0.24
        Assert.isFalse(yearRate.compareTo(BigDecimal.ZERO) < 0, tryExp("年化利率不合法"));
        Assert.isFalse(yearRate.compareTo(PERCENT_25) > 0, tryExp("年化利率不合法"));
        Assert.isFalse(invest.compareTo(NUM_100) < 0, tryExp("借款金额不合法"));
        Integer nowUserId = getNowUserId();
        log.info("用户id:{},订单号:{},进行计算分期,======================start===================", nowUserId, orderNo);
        //日利率 * 当月天数 = 月利率
        BigDecimal dayRate = yearRate.divide(NUM_360, SCALE, MODE);
        BigDecimal monthRate = yearRate.divide(NUM_12, SCALE, MODE);
        //日融担费计算-当综合年化利率超出时，就会有融担费
        BigDecimal dayfinancingRate = BigDecimal.ZERO;
        BigDecimal dayfinancingMonthRate = BigDecimal.ZERO;
        if (isFinan) {
            //融担分期数如果大于总分期数则异常
            if (financingPeriodsNum > periodsNum) {
                log.error("融担分期数不允许大于借款分期数");
                throw new PrometheusException("融担分期数不允许大于借款分期数");
            }
            Assert.isFalse(financingYearRate.compareTo(BigDecimal.ZERO) < 0, tryExp("融担费率不合法"));
            dayfinancingRate = financingYearRate.divide(NUM_360, SCALE, MODE);
            dayfinancingMonthRate = financingYearRate.divide(NUM_12, SCALE, MODE);
        }
        invest = invest.setScale(SCALE_2, MODE);
        BigDecimal perMonthPrincipalInterest = getPerMonthPrincipalInterest(invest, periodsNum, monthRate);
        log.info("每月本息：" + perMonthPrincipalInterest);
        LocalDate temp = ObjectUtil.clone(date);
        BigDecimal tempInvest = ObjectUtil.clone(invest).setScale(SCALE_2, MODE);
        LocalDate nexMonDate = getNexMonDate(date, oldRepayDate, repayDayPostpone, repayDayType);
        log.info("首次还款日：" + nexMonDate);
        BigDecimal count = new BigDecimal("0");
        //总融担费
        BigDecimal countFinancing = new BigDecimal("0");
        List<LoanPlanInfoVO> list = new ArrayList<>();
        for (int i = 1; i < periodsNum + 1; i++) {
            LoanPlanInfoVO vo = new LoanPlanInfoVO();
            //userid进行关联
            vo.setUserId(nowUserId);
            //序列号
            vo.setLoanOrderNo(orderNo);
            //借款金额
            vo.setLoanAmt(tempInvest);
            //状态为正常
            vo.setRepayStatus(BillRepayStatusConstants.NORMAL);
            //融担费率
            vo.setFinancingRate(financingYearRate);
            //第几期
            vo.setPeriodsNum(i);
            //还款时间
            vo.setPayableDate(nexMonDate);
            vo.setLoanDate(date);
            //这两个字段暂时不用
            vo.setOverdueDays(0);
            vo.setFinancingRate(BigDecimal.ZERO);
            vo.setPunishInterestRate(BigDecimal.ZERO);
            vo.setPayableFinancingAmt(BigDecimal.ZERO);
            //每月本息
            vo.setPrincipalInterestAmt(perMonthPrincipalInterest);
            //日利率,年利率
            vo.setDayRate(dayRate);
            vo.setYearRate(yearRate);
            //月利率计算
            long days = Math.abs(ChronoUnit.DAYS.between(temp, nexMonDate));
            monthRate = dayRate.multiply(new BigDecimal(days));
            //当月利息
            BigDecimal monthInterest = invest.multiply(monthRate).setScale(SCALE_2, MODE);
            BigDecimal monthFinancing = BigDecimal.ZERO;
            //如果有融担费，需要计算
            if (isFinan) {
                dayfinancingMonthRate = dayfinancingRate.multiply(new BigDecimal(days));
                //当月融担费
                monthFinancing = invest.multiply(dayfinancingMonthRate).setScale(SCALE, MODE);
            }
            //月应还款利息
            vo.setPayableInterestAmt(monthInterest);
            if (i < periodsNum) {
                //每月本金
                BigDecimal principalAmt = perMonthPrincipalInterest.subtract(monthInterest).setScale(SCALE_2, MODE);
                log.info("等额本息---" + i + "还款时间：" + nexMonDate + "--月还款本金:" + principalAmt + ",还款利息:" + monthInterest + ",融担费:" + monthFinancing);
                invest = invest.subtract(principalAmt);
                //月应还款本金
                vo.setPayablePrincipalAmt(principalAmt);
                //剩余本金
                vo.setSurplusPrincipalAmt(invest);
                //当月应还金额 本+息+融担
                vo.setPayableAmt(principalAmt.add(monthInterest).add(monthFinancing).setScale(SCALE_2, MODE));
            } else {
                log.info("等额本息---" + i + "还款时间：" + nexMonDate + "--月还款本金:" + invest + ",还款利息:" + monthInterest + ",融担费:" + monthFinancing);
                //月应还款本金
                vo.setPayablePrincipalAmt(invest);
                //剩余本金
                vo.setSurplusPrincipalAmt(BigDecimal.ZERO);
                //当月应还金额 本+息+融担
                vo.setPayableAmt(invest.add(monthInterest).add(monthFinancing).setScale(SCALE_2, MODE));
            }
            //将月份往后加一个月
            temp = nexMonDate;
            //下一个月的还款时间
            nexMonDate = nexMonDate.plusMonths(1);
            //总利息
            count = count.add(monthInterest);
            //总融担费
            countFinancing = countFinancing.add(monthFinancing);
            list.add(vo);
        }
        //总应还金额 = 总融担费 + 所有本息
        BigDecimal totalDueAmt = countFinancing.add(tempInvest).add(count).setScale(SCALE_2, MODE);
        list.forEach(vo -> vo.setTotalPayableAmt(totalDueAmt));
        //分摊融担费，如果有融担费，需要计算
        if (isFinan) {
            BigDecimal finanFee = countFinancing.divide(new BigDecimal(financingPeriodsNum), SCALE, MODE);
            for (LoanPlanInfoVO vo : list) {
                if (vo.getPeriodsNum() <= financingPeriodsNum) {
                    //应还融担费
                    vo.setPayableFinancingAmt(finanFee.setScale(SCALE_2, MODE));
                    //当月应还金额（本金+利息+融担）
                    vo.setPayableAmt(vo.getPayableAmt().add(finanFee).setScale(SCALE, MODE));
                    log.info("融担分期：融担日期：" + vo.getPayableDate() + "共：" + finanFee);
                }
            }
        }
        log.info("总利息：" + count);
        log.info("用户id:{},订单号:{},分期计算完成,======================end===================", userId, orderNo);
        return list;
    }

    /***
     * 获取userId
     * @return
     */
    private Integer getNowUserId() {
        return 0;
    }

    /***
     * 保存上一期剩余本金
     * @param planList
     * @return
     */
    private Map<String, BigDecimal> setSurplusLoanAmt(List<LoanPlanInfoVO> planList) {
        Map<String, BigDecimal> map = new HashMap<>();
        planList.forEach(plan -> {
            map.put(String.valueOf(plan.getPeriodsNum() + 1), plan.getSurplusPrincipalAmt());
            if (plan.getPeriodsNum().intValue() == 1) {
                map.put(String.valueOf(1), plan.getLoanAmt());
            }
        });
        return map;
    }

    /***
     * 如果是第一期则取实际借款时间
     * @param now
     * @param payableDate
     * @return
     */
    private Long setOverdueDays(LocalDate now, LocalDate payableDate) {
        return Math.abs(ChronoUnit.DAYS.between(now, payableDate));
    }

    /**
     * 计算逾期的利息
     *
     * @param now
     * @param dueDateMap
     * @param plan
     * @return
     */
    private Long interestDays(LocalDate now, Map<String, LocalDate> dueDateMap, LoanPlanInfoVO plan) {
        //如果首次还款日期为空 那么时间即为借款时间
        LocalDate lastRepayDate = null;
        if (ObjectUtil.isNull(dueDateMap.get(LAST_REPAY_DATE))) {
            lastRepayDate = plan.getLoanDate();
        } else {
            lastRepayDate = dueDateMap.get(LAST_REPAY_DATE);
        }
        return Math.abs(ChronoUnit.DAYS.between(now, lastRepayDate));
    }


    /**
     * 针对线下计算逾期还款计划
     *
     * @param planList           原始逾期还款计划
     * @param punishInterestRate 罚息
     * @param punishSubject      主体
     * @param lastRepayDateTime  最近还款日
     * @param offlineRepayDate   线下还款时间
     * @return
     */
    @ApiModelProperty("针对线下计算逾期计划")
    public List<LoanPlanInfoVO> calculatorOverdueOffline(List<LoanPlanInfoVO> planList,
                                                            BigDecimal punishInterestRate,
                                                            String punishSubject,
                                                            String lastRepayDateTime,
                                                            String offlineRepayDate) {
        Assert.isFalse(CollUtil.isEmpty(planList), tryExp("请输入有效的还款计划"));
        Assert.isFalse(!CommonConstants.PUNISH_SUB_LIST.contains(punishSubject), tryExp("罚息主体不合法"));
        Assert.isFalse(punishInterestRate.compareTo(BigDecimal.ZERO) < 0, tryExp("罚息利率不合法"));
        Assert.notNull(offlineRepayDate, tryExp("线下还款时间不能为空"));
        LoanPlanInfoVO vo = planList.get(0);
        Integer userId = vo.getUserId();
        String orderNo = vo.getLoanOrderNo();
        log.info("before--用户id:{},订单号:{},罚息开始计算,======================start===================", userId, orderNo);
        log.info("before--计算前完整计划为:{}", JSONUtil.toJsonStr(planList));
        //日罚息
        BigDecimal dayPunishInterestRate = punishInterestRate.divide(NUM_360, SCALE, MODE);
        //1、当期是否逾期，用于计算逾期天数
        //2、当期是否结清，当期结清从下一起开始计算
        //3、如果罚息主体是当期本金，那么需要对已经逾期的期数进行计算罚息，分开挂在月份期数上
        //4、如果罚息主体是所有本金，那么需要对所有本金进行计算罚息，并只需要挂在当前要还的月份期数上
        Map<String, LocalDate> dueDateMap = new HashMap<>();
        // 线下还款时间
        LocalDate now = DateUtil.parseLocalDateTime(offlineRepayDate).toLocalDate();
        //最后一次还款时间
        LocalDate lastRepayDate = putLastRepayDate(lastRepayDateTime, dueDateMap);
        log.info("最后一次还款时间:{}", lastRepayDate);
        //保存上一期剩余本金
        Map<String, BigDecimal> preSurplusMap = setSurplusLoanAmt(planList);
        //罚息主体为---当期本金为
        currentPrincipal(planList, punishSubject, now, dayPunishInterestRate, dueDateMap);
        //将逾期的利息挂在第一个逾期的头上
        resetInterestAmt(planList, now, dueDateMap, preSurplusMap);
        //罚息主体为---剩余本金
        surplusPrincipal(planList, punishSubject, now, dayPunishInterestRate, preSurplusMap, lastRepayDate);
        //处理第一个没有逾期的利息，从正常的期数开始查找，第一个正常的期数
        resetPayableInterest(planList, now, dueDateMap, preSurplusMap);
        //重新计算应还金额
        resetPayableAmt(planList);
        log.info("after--用户id:{},订单号:{},罚息计算完成,======================end===================", userId, orderNo);
        log.info("after--计算后完整计划为:{}", JSONUtil.toJsonStr(planList));
        return planList;
    }
}  
